#include <jni.h>
#include <string>
#include <android/bitmap.h> //把c语言中的bitmap转换成java语言中可操作的bitmap

extern "C" {
#include "gif_lib.h"
}
//8  1   16  2  24  3
//1.7.定义结构体
struct GifBean {
    int current_frame;
    int total_frame;
};

//argb函数：argb的摆放，将字节组装成int
#define argb(a, r, g, b) ( ((a) & 0xff) << 24 ) | ( ((b) & 0xff) << 16 ) | ( ((g) & 0xff) << 8 ) | ((r) & 0xff)
extern "C"
JNIEXPORT jlong JNICALL
Java_com_jack_nativeloadgif_GifLoadHelper_loadGif(JNIEnv *env, jobject thiz, jstring path_) {
    //1.1.将java中的String转为 成 c++中可以使用的string，参数2：传入0表示不copy（一般传入NULL）
    //https://blog.csdn.net/GCE7212201/article/details/53767507
    const char *path = env->GetStringUTFChars(path_, 0);

    int Erro;//根据Erro来判断文件是打开失败还是成功

    //1.3.使用 gif_lib 中的函数
    //返回值是一个结构体指针
    GifFileType *gifFileType = DGifOpenFileName(path, &Erro);

    //1.5.了解一下结构体的信息
    //     typedef struct GifFileType {
    //    GifWord SWidth, SHeight;         /* Size of virtual canvas */
    //    GifWord SColorResolution;        /* How many colors can we generate? */
    //    GifWord SBackGroundColor;        /* Background color for virtual canvas */
    //    GifByteType AspectByte;	     /* Used to compute pixel aspect ratio */
    //    ColorMapObject *SColorMap;       /* Global colormap, NULL if nonexistent.  gif会把所有的颜色放在一个字典里面，同一种颜色就存一个像素，下一个像素就存索引 */
    //    int ImageCount;                  /* Number of current image (both APIs) */  帧数
    //    GifImageDesc Image;              /* Current image (low-level API) */
    //    SavedImage *SavedImages;         /* Image sequence (high-level API) 颜色字典的索引，可以理解成压缩数据 */
    //    int ExtensionBlockCount;         /* Count extensions past last image */
    //    ExtensionBlock *ExtensionBlocks; /* Extensions past last image */
    //    int Error;			     /* Last error condition reported */
    //    void *UserData;                  /* hook to attach user data (TVT) */     可以理解成一个tag
    //    void *Private;                   /* Don't mess with this! */
    //} GifFileType;

    //1.6.初始化缓冲区  数组 SaveImages
    DGifSlurp(gifFileType);

    //1.8 malloc：用于动态分配内存空间；    c代码 使用 malloc，c++使用new
    GifBean *gifBean = static_cast<GifBean *>(malloc(sizeof(GifBean)));
    //1.9.清空操作  memset是计算机中C/C++语言初始化函数。作用是将某一块内存中的内容全部设置为指定的值， 这个函数通常为新申请的内存做初始化工作。
    memset(gifBean, 0, sizeof(GifBean));

    //1.20.赋值操作             将gifBean赋值给了gifFileType中的UserData，方便后面去取
    gifFileType->UserData = gifBean;
    gifBean->current_frame = 0;
    //1.21.总帧数：  从gifFileType->ImageCount 中获取
    gifBean->total_frame = gifFileType->ImageCount;

    //1.2.释放操作
    //函数作用：来释放拷贝的内容
    //参数1:jstring str 指定一个jstring变量，即是要释放的本地字符串的来源
    //参数2：const char* chars 就是要释放的本地字符串
    env->ReleaseStringUTFChars(path_, path);
//    return (jlong) (gifFileType);//这种写法也可以
    return reinterpret_cast<jlong>(gifFileType);    //1.4.强转一下，返回给java层
}

//前提:需要将dgif_lib.c和gifalloc.c 添加到add_library中
//add_library(
//             native-lib
//             SHARED
//
//             native-lib.cpp
//        dgif_lib.c
//        gifalloc.c)

int delay(ExtensionBlock *ext) {
    return (10 * ((ext)->Bytes[2] << 8 | (ext)->Bytes[1]));
}

extern "C"
JNIEXPORT jint JNICALL
Java_com_jack_nativeloadgif_GifLoadHelper_getWidth(JNIEnv *env, jobject thiz,
                                                   jlong gif_load_helper) {
    //2.1.获取宽
    GifFileType *gifFileType = reinterpret_cast<GifFileType *>(gif_load_helper);
    return gifFileType->SWidth;
}

extern "C"
JNIEXPORT jint JNICALL
Java_com_jack_nativeloadgif_GifLoadHelper_getHeight(JNIEnv *env, jobject thiz, jlong gif_point) {
    //2.2.获取高
    GifFileType *gifFileType = reinterpret_cast<GifFileType *>(gif_point);
    return gifFileType->SHeight;
}

/**
 * 像素渲染器，通过读取 GIF 图像的帧数据和颜色信息，并将这些信息解析和转换为 Bitmap 上的像素，实现了 GIF 图像在 Android 平台的播放和渲染
 * @param gifFileType
 * @param info
 * @param pixels
 */
int drawFrame(GifFileType *gifFileType, AndroidBitmapInfo info, void *pixels) {
    //从 gifFileType 中获取 UserData，这个 UserData 在加载 GIF 时被设置为 gifBean，它包含了 GIF 的相关信息。
    GifBean *gifBean = static_cast<GifBean *>(gifFileType->UserData);
    //当前帧 一定要拿到         拿到当前帧   直接
    //1.获取当前帧的SavedImage（包含了图片的描述和图片的像素信息）
    //获取当前帧的图像信息。
    SavedImage savedImage = gifFileType->SavedImages[gifBean->current_frame];

    //2.解压像素信息
    //2.1.获取像素的描述
    //从当前帧的图像信息中获取像素信息。
    GifImageDesc frameInfo = savedImage.ImageDesc;
    //8.5.像素字典
    //获取当前帧的颜色映射对象。
    ColorMapObject *colorMapObject = frameInfo.ColorMap;

    //问题1       不增加会存在报错   使用ndk的方法进行排查
    //这段代码的作用是为当前帧获取颜色映射对象

    //需要考虑到 GIF 图像的每一帧是否都使用了自定义的颜色映射表。如果你确定每一帧都有自己的颜色映射表，并且不会使用全局的颜色映射表，那么可以去掉这段代码。
    //然而，大部分的 GIF 图像中，每一帧都会继承全局的颜色映射表，但也可能有少数帧使用了自定义的颜色映射表。
    //因此，保留这段代码是为了确保对于那些使用了自定义颜色映射表的帧能够正确地获取到颜色信息，以便准确地渲染图像。

    //在 GIF 图像中，每一帧都可以有自己的颜色映射表，即调色板，用于映射图像中的颜色索引到实际的颜色值上。
    //首先检查当前帧（frameInfo）是否有自己的颜色映射表（ColorMap）
    //如果当前帧的 ColorMap 不为空，说明该帧有自定义的颜色映射表，那么就将这个颜色映射表赋给 colorMapObject 变量。
    if (frameInfo.ColorMap) {
        colorMapObject = frameInfo.ColorMap;
    } else {
        //如果当前帧没有自己的颜色映射表，那么它将使用全局的颜色映射表，即 gifFileType->SColorMap，来获取颜色映射对象。
        //没有的话就获取全局的颜色列表
        colorMapObject = gifFileType->SColorMap;
    }

    //4.pixels是即将被渲染的bitmap   这里定义int类型的指针，是因为bitmap默认是ARGB_8888格式，ARGB_8888一个像素占4个字节
    //4.1.定义px，记录每一行的首地址
    //将 pixels 强制类型转换为 int 指针，准备将像素渲染到 Bitmap 上。
    int *px = (int *) pixels;

    //新增
    ExtensionBlock *ext;
    //循环遍历扩展块，用于处理 GIF 图像的扩展信息。
    for (int i = 0; i < savedImage.ExtensionBlockCount; ++i) {
        //根据条件寻找图像中的图形控制扩展块（Graphics Control Extension Block）。这个扩展块包含了图像控制的信息，比如图像的延迟时间、透明度等。
        //savedImage.ExtensionBlocks：是当前帧的扩展块数组
        //通过遍历这些扩展块，代码尝试找到一个特定类型的扩展块，即图形控制扩展块。
        //GRAPHICS_EXT_FUNC_CODE ：可能是一个预定义的常量，代表了图形控制扩展块的功能码。这个常量可能在代码其他地方被定义。
        if (savedImage.ExtensionBlocks[i].Function == GRAPHICS_EXT_FUNC_CODE) {
            //当找到功能码与图形控制扩展块功能码匹配的扩展块时，代码会将这个扩展块的指针存储在变量 ext 中，以便后续处理。
            //这个图形控制扩展块中的信息包括控制帧显示的时间间隔、是否使用透明色等，对于 GIF 图像的正确显示和控制至关重要。
            ext = &(savedImage.ExtensionBlocks[i]);
            break;
        }
    }

    //5.临时索引
    int *line;
    //4.定义一个像素索引
    int pointPixel;
    GifByteType gifByteType;
    //10.经过解压后的像素
    GifColorType gifColorType;

    //问题2
    //定位到像素数据的起始位置，用于绘制当前帧的图像。
    //如果 GIF 图像的当前帧并不是从图像数据的开头开始（例如，GIF 中某些帧只更新了图像的一部分，不是整个图像），那么这个偏移量就是非零的，需要通过偏移来正确地定位到像素数据的开始位置。
    //以保证正确的处理所有类型的 GIF 图像
    px = (int *) ((char *) px + info.stride * frameInfo.Top);//这行注释掉会存在着花屏的问题
    //3.解压像素
    //循环遍历图像的像素行
    for (int y = frameInfo.Top; y < frameInfo.Top + frameInfo.Height; ++y) {        //y是行  x是列
        //6.每次遍历行，将首地址传给line
        line = px;
        for (int x = frameInfo.Left; x < frameInfo.Left + frameInfo.Width; ++x) {   //从左侧开始，到右侧结束
            //8.定位到像素    y是垂直方向，x是水平方向
            pointPixel = (y - frameInfo.Top) * frameInfo.Width + (x - frameInfo.Left);

            //问题3：
            //如果 GIF 图像中使用了图形控制扩展块并且对特定值进行了设置（比如透明色等），保留这段代码可以确保渲染过程正确地考虑了这些特殊情况，以获得正确的渲染结果
            //条件中的 savedImage.RasterBits[pointPixel] == ext->Bytes[3]: 在比较当前像素的颜色索引值是否与图形控制扩展块中的指定值相匹配，ext->Bytes[0] 确保了扩展块存在。
            //如果满足条件，就会跳过当前像素的渲染，而不会对该像素进行颜色值的设定。
            //确认 GIF 图像中是否包含了图形控制扩展块（Graphics Control Extension Block）以及是否对图形控制扩展块中的特定值进行了设置
            //ext->Bytes[3] 代表图形控制扩展块中的第四个字节,在某些 GIF 图像中，这个字节可能用于表示透明色的索引值。
            //ext->Bytes[3] 和 ext->Bytes[0] 的含义取决于 GIF 图像的编码方式以及图形控制扩展块中的定义。在不同的情况下，这些字节可能会有不同的作用，需要参考相关的 GIF 图像规范或文档来确定其具体含义。
            //ext->Bytes[0] 在特定上下文中可能代表着一个控制标志位或者特定的条件
            if (savedImage.RasterBits[pointPixel] == ext->Bytes[3] && ext->Bytes[0]) {
                continue;
            }

            //8.1.savedImage.RasterBits[i]：包含了像素信息，但是是一个一位数组
            //8.2.通过索引找到像素
            gifByteType = savedImage.RasterBits[pointPixel];
            //8.4.gifByteType：是压缩数据，不能直接拿来使用
            gifColorType = colorMapObject->Colors[gifByteType];
            //11.只要对line进行赋值，屏幕上就会有颜色
            line[x] = argb(255, gifColorType.Red, gifColorType.Green, gifColorType.Blue);
        }
        //7.info.stride:表示一行的字节数
        //7.1.先将px转成char*类型，再与一行的字节数相加，就可以到达下一行  //移动到下一行的像素数据起始位置
        px = (int *) ((char *) px + info.stride);
    }

    return delay(ext);
}

extern "C"
JNIEXPORT jint JNICALL
Java_com_jack_nativeloadgif_GifLoadHelper_updateFrame(JNIEnv *env, jobject thiz, jlong gif_point,
                                                      jobject bitmap) {
//extern "C"
//JNIEXPORT jint JNICALL
//Java_com_jack_nativeloadgif_GifLoadHelper_updateFrame(JNIEnv *env, jclass clazz, jlong gif_point,
//                                                      jobject bitmap) {
    //获取bitmap的宽高，通过gif_point进行获取
    GifFileType *gifFileType = reinterpret_cast<GifFileType *>(gif_point);
//    int width = gifFileType->SWidth;
//    int height = gifFileType->SHeight;

    //根据bitmap 获取 宽高
    //1.或通过AndroidBitmapInfo获取宽高
    AndroidBitmapInfo info;
    AndroidBitmap_getInfo(env, bitmap, &info);

    //1.1.宽高信息
//    width = info.width;
//    height = info.height;

    //2.将bitmap转成数组

    void *pixels;
    //下方代码两个作用：其一，将bitmap转成像素二维数组，其二锁住当前bitmap
    AndroidBitmap_lockPixels(env, bitmap, &pixels);
    //3.绘制数组
    int delay = drawFrame(gifFileType, info, pixels);
    AndroidBitmap_unlockPixels(env, bitmap);

    //4.得到解码后的帧，并进行强转
    GifBean *gifBean = static_cast<GifBean *>(gifFileType->UserData);   //问：这里为什么可以进行强转(在加载的时候提前设定的)
    gifBean->current_frame++;
    //5.循环播放
    if (gifBean->current_frame >= gifBean->total_frame - 1) {
        gifBean->current_frame = 0;
    }
    return delay;
}
